#ifndef __M_FMT_H__
#define __M_FMT_H__
#include "level.hpp"
#include "message.hpp"
#include <memory>
#include <ctime>
#include <vector>
#include<cassert>
#include<sstream>
// 格式化子项类
namespace kaizi
{
    // 1、抽象格式化子项基类
    class FormatItem
    {
    public:
        using ptr = std::shared_ptr<FormatItem>;                 // 对FormatItem进行智能管理
        virtual void format(std::ostream &out, const LogMsg &msg) = 0; // 纯虚函数
    };
    // 2、派生格式化子项子类---消息、等级、时间、文件名、行号、线程ID、日志器名称、制表符、换行、其他
    class MsgFormatItem : public FormatItem // 消息主体
    {
    public:
        void format(std::ostream &out, const LogMsg &msg) override
        {
            out << msg._payload;
        }
    };
    class levelFormatItem : public FormatItem // 日志等级
    {
    public:
        void format(std::ostream &out, const LogMsg &msg) override
        {
            out << LogLevel::toString(msg._level); // 利用之前写的日志等级代码进行日志等级的转换然后将转换后的结果输出进去
        }
    };
    class TimeFormatItem : public FormatItem // 格式化日志中的时间
    {
    public:
        TimeFormatItem(const std::string &fmt = "%H:%M:%S") // 构造函数，把传入的格式化字符串赋值给私有成员变量 _time_fmt
            : _time_fmt(fmt)
        {
        }
        void format(std::ostream &out, const LogMsg &msg) override
        {
            // tm 是 C/C++ 标准库的时间结构体
            // 先定义一个 tm 结构体变量 t ，tm 结构体用于存储分解后的时间信息
            struct tm t;
            // 把 msg._ctime 里的时间戳转换成本地时间，并填充到 tm 结构体 t 中。
            localtime_r(&msg._ctime, &t);
            // 定义一个字符数组 tmp，用来存储最终格式化的时间字符串
            char tmp[32] = {0};
            // strftime 是时间格式化核心函数，按规则把 tm 结构体的时间转成字符串
            strftime(tmp, 31, _time_fmt.c_str(), &t);
            // 把格式化好的时间字符串（存在 tmp），输出到日志流 ou
            out << tmp; // 输出日志的时间
        }

    private:
        std::string _time_fmt; //%H:%M:%S
    };

    class FileFormatItem : public FormatItem // 文件名
    {
    public:
        void format(std::ostream &out, const LogMsg &msg) override
        {
            out << msg._file; // 直接取出文件名添加进去就行了
        }
    };

    class LineFormatItem : public FormatItem // 行号
    {
    public:
        void format(std::ostream &out, const LogMsg &msg) override
        {
            out << msg._line; // 直接取出一个数字就行了
        }
    };

    class ThreadFormatItem : public FormatItem // 线程ID
    {
    public:
        void format(std::ostream &out, const LogMsg &msg) override
        {
            out << msg._tid; // 直接取出线程ID就行了
        }
    };
    class LoggerFormatItem : public FormatItem // 日志器名称
    {
    public:
        void format(std::ostream &out, const LogMsg &msg) override
        {
            out << msg._logger; // 直接取出日志器名称就行了
        }
    };

    class TabFormatItem : public FormatItem // 制表符
    {
    public:
        void format(std::ostream &out, const LogMsg &msg) override
        {
            out << "\t";
        }
    };

    class NLineFormatItem : public FormatItem // 换行
    {
    public:
        void format(std::ostream &out, const LogMsg &msg) override
        {
            out << "\n";
        }
    };
    //  abcdefg[%d{%H}]  这里的话abcdefg[就属于其他字符串了
    class OtherFormatItem : public FormatItem // 其他
    {
    public:
        OtherFormatItem(const std::string &str) // 构造函数，把传入的字符串赋值给私有成员变量 _str
            : _str(str)
        {
        }
        void format(std::ostream &out,const LogMsg &msg) override
        {
            out << _str;
        }

    private:
        std::string _str; // 原始字符串
    };

    /*
    %d表示日期，包含子格式{%H:%M:%S}
    %t表示线程ID
    %c表示日志器名称
    %f表示文件名
    %l表示行号
    %p表示日志级别
    %T是制表符缩进
    %m表示消息主体
    %n表示换行符


    */
//将用户提供的格式字符串（如 [%d{%H:%M:%S}][%t][%c]...）解析为一系列 FormatItem 对象。
    class Formatter
    {
    public:
        using ptr=std::shared_ptr<Formatter>; // 对Formatter进行智能管理
        Formatter(const std::string &pattren = "[%d{%H:%M:%S}][%t][%c][%f:%l][%p]%T%m%n")
            : _pattern(pattren)
        {
            assert(parsePattern());//断言格式化必须成功
        }
        //下面两行代码是对msg进行格式化
        void format(std::ostream &out, const LogMsg &msg)//format重载，将消息放到io流中，我们对IO流进行处理操作
        {
            //逐个遍历_items，从消息中取出内容进行一个format。得到一个数据就放到ostream中
            for(auto &item:_items)//遍历 _items 容器，_items 里存的是各种 FormatItem 子类的智能指针
            {
                //调用每个 FormatItem 子类重写的 format 方法，把 msg 里的对应数据
                //按照该子类的规则格式化后，输出到 out 流里
                item->format(out,msg);//这个format是基类的虚函数，我们在派生类中实现，这里我们直接调用基类的实现
            }
        }
        std::string format(const LogMsg &msg) // 对这条消息进行格式化操作，返回格式化后的字符串
        {
            //创建一个 stringstream，它是内存中的流，用来临时存储格式化后的日志内容。
            std::stringstream ss;
            format(ss,msg);//调用上面的接口，将日志内容格式化后输出到ss这个内存流中
            return ss.str(); // 返回格式化后的字符串
        }
    private:
        //对格式化规则字符串进行解析
        bool parsePattern() // 解析格式化规则字符串，将其解析成格式化子项数组
        {
            //1.对格式化规则字符串进行解析
            std::vector<std::pair<std::string,std::string>> fmt_order; // 定义一个字符串数组，用来存储解析后的格式化子项
            size_t pos=0;
            std::string key,val;
            while(pos<_pattern.size())
            {
                //1. 处理原始字符串---判断是不是%，不是就是原始字符
                
                if(_pattern[pos]!='%')
                {
                    // 不是%，直接输出原始字符
                    val.push_back(_pattern[pos++]);//然后就是pos++继续往后面走了
                    continue;
                }
                //能到这里说明pos位置就是%的，那么我们就得判断pos后面是不是%了,%%处理成为一个原始%字符
                if(pos+1<_pattern.size() && _pattern[pos+1]=='%')//说明是两个%
                {
                    val.push_back('%');
                    pos+=2;//往后面走两步，越过这两个%
                    continue;
                }
                //这个时候代表我们原始字符串处理完毕
                if(val.empty()==false)//不为空的情况下
                {
                    fmt_order.push_back(std::make_pair("",val)); // 加入到格式化子项数组中
                    val.clear(); // 清空val
                }
                

                //这个时候是格式化字符的处理
                pos+=1;//往后面走一步，跳过%
                //走到了这里说明不是一个原始字符，说明后面是格式化字符了
                if(pos==_pattern.size())//如果pos已经到末尾了，说明格式化字符串格式不对
                {
                    std::cout<<"%之后没有格式化字符"<<std::endl;
                    return false;
                }
                key=_pattern[pos];//取出%
                pos+=1;//往后面走一步，跳过%
                //这个时候，pos指向格式化字符后的位置，
                //如果这个位置是{，说明后面还有子格式化字符串
                
                if(pos<_pattern.size()&&_pattern[pos]=='{')
                {
                    
                    pos+=1;//这个时候pos指向子格式化字符串的开始位置
                    while(pos<_pattern.size()&&_pattern[pos]!='}')
                    {
                        val.push_back(_pattern[pos++]);
                    }
                    //走到了末尾跳出循环，没有遇到}，说明格式化字符串格式不对
                    if(pos==_pattern.size())//走到末尾了，都没找到}，说明格式化字符串格式不对
                    {
                        std::cout<<"子规则花括号匹配错误"<<std::endl;
                        return false;
                    }
                    pos+=1;//因为这个时候pos指向了}，所以pos+1，跳过}
                    //向后走一步，走到下次处理的新位置
                }
                fmt_order.push_back(std::make_pair(key,val)); // 加入到格式化子项数组中
                key.clear(); // 清空key
                val.clear(); // 清空val
                //两个内容都进行清空操作，进行下一轮循环操作

            }
            //到这里我们就得到了处理结果了，我们就得进行解析了结果了
            //2.根据解析得到的数据初始化子项数组成员
            for(auto &it:fmt_order)//fmt_order是上面while循环的处理结果。我们进行遍历操作
            {
                //fmt_order是一个pair类型的，我们上面结束函数结束的时候是将(key,val)插入到里面了
                //我们这里遍历了fmt_order中的每一个pair类型进行子项对象的创建，并且插入到items数组中

                //并且我们在创建子项对象的时候，都返回了对应的数据了，然后将数据存储在items中，最后就是我们的日志了

                //往这个指针数组中加入对应的格式化子项对象
                _items.push_back(createItem(it.first,it.second)); // 根据格式化子项的类型字符串，创建对应的格式化子项对象，并加入到 _items 容器中
            }
            return true;
            
        }
   
        //根据不同的格式化字符创建不同的格式化子项对象
        FormatItem::ptr createItem(const std::string &key,const std::string &val) // 根据格式化子项的类型字符串，创建对应的格式化子项对象，并返回智能指针
        {
            /*
            %d表示日期，包含子格式{%H:%M:%S}
            %t表示线程ID
            %c表示日志器名称
            %f表示文件名
            %l表示行号
            %p表示日志级别
            %T是制表符缩进
            %m表示消息主体
            %n表示换行符
            */
            if(key == "d") return std::make_shared<TimeFormatItem>(val);// 如果是日期格式化子项
            if(key == "t") return std::make_shared<ThreadFormatItem>();// 如果是线程ID格式化子项
            if(key == "c") return std::make_shared<LoggerFormatItem>();// 如果是日志器名称格式化子项
            if(key == "f") return std::make_shared<FileFormatItem>();// 如果是文件名格式化子项
            if(key == "l") return std::make_shared<LineFormatItem>();// 如果是行号格式化子项
            if(key == "p") return std::make_shared<levelFormatItem>();// 如果是日志级别格式化子项
            if(key == "T") return std::make_shared<TabFormatItem>();// 如果是制表符格式化子项
            if(key == "m") return std::make_shared<MsgFormatItem>();// 如果是消息主体格式化子项
            if(key == "n") return std::make_shared<NLineFormatItem>();// 如果是换行符格式化子项
            if(key=="")// 如果是空字符串，说明是其他格式化子项
            {
                return std::make_shared<OtherFormatItem>(val);// 如果是其他格式化子项
            }
            std::cout<<"没有对应的格式化字符：%"<<key<<std::endl;
            abort();//程序异常退出处理
            return FormatItem::ptr();//空对象指针
        }
    private:
        std::string _pattern;                // 格式化规则字符串
        std::vector<FormatItem::ptr> _items; // 这个容器中的类型是智能指针 ，存储的是格式化子项的智能指针
        /*
对这个子项数组按顺序的从我们的消息中取出对应的内容
逐个遍历_items，从消息中取出内容进行一个format。得到一个数据就放到ostream中
*/
    
    };

}

#endif